언어 서버 프로토콜
1. 개요
1. 개요
언어 서버 프로토콜(Language Server Protocol, LSP)은 통합 개발 환경(IDE)이나 소스 코드 편집기와 같은 클라이언트와 언어 서버 간의 통신을 위한 개방형 프로토콜이다. 이 프로토콜은 JSON-RPC를 기반으로 하여, 코드 완성, 정의로 이동, 모든 참조 찾기, 오류 진단(린팅), 문서 서식 지정 등 다양한 언어 기능을 제공하는 표준화된 방법을 정의한다.
이 프로토콜은 2016년 마이크로소프트에 의해 주도되어 최초로 등장했다. 핵심 아이디어는 언어 지원 기능을 구현하는 도구(언어 서버)와 사용자가 상호작용하는 편집기를 분리하는 것이다. 이를 통해 언어 도구 개발자는 여러 편집기와 통합 개발 환경을 위해 각각 다른 플러그인이나 확장 기능을 만들 필요 없이, 단일 언어 서버만 구현하면 된다.
결과적으로, Visual Studio Code, IntelliJ IDEA, Vim, Emacs 등 서로 다른 편집기를 사용하는 개발자들이 동일한 수준의 고급 언어 기능을 일관되게 이용할 수 있게 된다. 이는 개발 생산성을 크게 향상시키고, 언어 도구 생태계의 발전을 촉진하는 데 기여했다.
2. 주요 개념
2. 주요 개념
2.1. 클라이언트와 서버
2.1. 클라이언트와 서버
언어 서버 프로토콜의 아키텍처는 클라이언트-서버 모델을 기반으로 한다. 이 모델에서 클라이언트는 일반적으로 통합 개발 환경이나 소스 코드 편집기이며, 서버는 특정 프로그래밍 언어에 대한 지능형 기능을 제공하는 독립적인 프로세스이다.
클라이언트의 역할은 사용자가 코드를 작성하고 편집하는 프론트엔드 인터페이스를 제공하는 것이다. 사용자가 문서를 열거나 내용을 수정할 때, 클라이언트는 이러한 변경 사항을 서버에 알리고, 서버로부터 계산된 정보(예: 코드 완성 목록, 오류 진단)를 받아 사용자에게 시각적으로 표시한다. 반면, 서버는 백엔드에서 실제 언어 분석 작업을 수행한다. 이는 구문 분석, 정적 분석, 컴파일러 기술 등을 활용하여 코드의 의미를 이해하고, 클라이언트의 요청에 따라 다양한 언어 기능에 대한 응답을 생성한다.
이러한 분리는 핵심적인 장점을 제공한다. 언어 도구 개발자는 Visual Studio Code, Vim, 이클립스 등 다양한 코드 편집기를 모두 지원하기 위해 각기 다른 플러그인을 만들 필요가 없다. 대신 하나의 언어 서버만 구현하면 되며, 각 클라이언트(편집기)는 동일한 프로토콜을 통해 이 서버와 통신하면 된다. 이는 개발 생산성을 크게 높이고, 언어 지원의 일관성을 유지하는 데 기여한다.
2.2. 통신 방식
2.2. 통신 방식
언어 서버 프로토콜의 통신은 JSON-RPC (버전 2.0)을 기반으로 한다. 이는 경량의 원격 프로시저 호출 프로토콜로, JSON 형식으로 메시지를 주고받는다. 통신은 일반적으로 표준 입력과 표준 출력을 통해 이루어지며, 이는 서버 프로세스를 쉽게 시작하고 통신 채널을 설정할 수 있게 한다. 일부 구현에서는 소켓이나 파이프와 같은 다른 전송 방식을 사용하기도 한다.
클라이언트와 서버 간의 모든 상호작용은 요청과 응답, 또는 알림 메시지로 구성된다. 클라이언트가 textDocument/completion과 같은 특정 기능을 요청하면, 서버는 해당 요청을 처리하고 결과를 JSON 형식의 응답으로 반환한다. 서버는 또한 textDocument/publishDiagnostics와 같은 알림을 자발적으로 보내 클라이언트에게 문서의 오류나 경고를 실시간으로 보고할 수 있다.
이 JSON-RPC 기반의 통신 방식은 프로토콜을 언어와 플랫폼에 독립적으로 만드는 핵심 요소이다. 클라이언트(예: 비주얼 스튜디오 코드, Vim, 이클립스)와 서버는 각각 선호하는 프로그래밍 언어로 구현될 수 있으며, 표준화된 메시지 형식만을 통해 상호작용한다. 이로 인해 통합 개발 환경과 언어 지원 도구의 개발이 분리되어, 효율성과 유연성이 크게 향상되었다.
2.3. 프로토콜 메시지
2.3. 프로토콜 메시지
언어 서버 프로토콜의 모든 통신은 JSON-RPC를 기반으로 이루어진다. 이는 경량의 원격 프로시저 호출 프로토콜로, 클라이언트와 서버 간에 요청, 응답, 알림 메시지를 교환하는 표준화된 방식을 제공한다. 통신은 일반적으로 표준 입력과 표준 출력을 통해 이루어지며, 네트워크 소켓을 통한 연결도 가능하다.
프로토콜 메시지는 크게 요청, 응답, 알림의 세 가지 유형으로 구분된다. 클라이언트가 서버에 특정 작업(예: 텍스트 문서의 정의 위치 요청)을 수행하도록 보내는 것이 요청이며, 서버는 이에 대한 결과를 담은 응답을 회신한다. 반대로 서버가 클라이언트에게 특정 이벤트(예: 문서의 진단 오류 정보 발행)를 일방적으로 알리는 것은 알림 메시지에 해당한다. 모든 메시지는 JSON 형식으로 직렬화되어 전송된다.
주요 메시지 구조는 jsonrpc, id, method, params, result 등의 필드로 구성된다. jsonrpc 필드는 항상 "2.0" 값을 가지며 프로토콜 버전을 명시한다. id는 요청과 응답을 짝지어주는 식별자로 사용된다. method는 호출하려는 기능(예: textDocument/completion, textDocument/definition)을 지정하고, params는 해당 메서드에 필요한 매개변수(예: 문서 URI, 커서 위치)를 포함한다. 서버의 응답은 result 필드에 요청된 데이터를 담아 보내거나, 오류 발생 시 error 필드에 상세 정보를 제공한다.
이러한 표준화된 메시지 체계는 마이크로소프트가 주도하여 정의한 프로토콜 사양에 명시되어 있으며, 다양한 프로그래밍 언어로 구현된 언어 서버와 통합 개발 환경 클라이언트가 상호 운용될 수 있는 기반이 된다.
3. 핵심 기능
3. 핵심 기능
3.1. 텍스트 동기화
3.1. 텍스트 동기화
텍스트 동기화는 언어 서버 프로토콜의 가장 기본적이고 필수적인 기능이다. 이는 클라이언트인 통합 개발 환경이나 소스 코드 편집기에서 열린 문서의 내용이 서버인 언어 서버와 항상 일치하도록 유지하는 메커니즘을 말한다. 사용자가 코드를 편집할 때마다 변경 사항이 실시간으로 서버에 전달되어, 서버가 최신 코드를 기반으로 정확한 분석과 정보 제공을 할 수 있게 한다.
동기화는 주로 textDocument/didOpen, textDocument/didChange, textDocument/didClose와 같은 알림 메시지를 통해 이루어진다. 문서가 열리면 클라이언트는 전체 내용을 서버에 전송하고, 이후 발생하는 모든 변경(문자 추가, 삭제, 수정)은 증분 방식으로 서버에 보고된다. 이를 통해 서버는 효율적으로 내부 문서 모델을 업데이트하고, 메모리 사용량과 네트워크 대역폭을 최적화할 수 있다.
이러한 실시간 동기화는 코드 완성, 정의로 이동, 진단 정보 제공 등 다른 모든 고급 기능의 토대가 된다. 서버가 오래된 코드 버전을 참조한다면 제공하는 정보의 정확성이 떨어지기 때문이다. 따라서 텍스트 동기화는 언어 서버 프로토콜이 지향하는 풍부한 편집 환경을 구현하기 위한 핵심 선행 조건이다.
3.2. 코드 완성
3.2. 코드 완성
코드 완성은 언어 서버 프로토콜의 핵심 기능 중 하나로, 개발자가 코드를 입력하는 과정에서 문맥에 맞는 변수명, 함수명, 키워드, 모듈 경로 등을 제안하는 기능을 말한다. 이 기능은 textDocument/completion 요청을 통해 동작하며, 클라이언트인 통합 개발 환경이나 소스 코드 편집기가 사용자의 입력 위치를 서버에 전달하면, 언어 서버는 해당 위치의 문법과 의미를 분석하여 완성 항목 목록을 응답으로 돌려준다.
제안되는 완성 항목에는 각 항목의 종류(예: 함수, 변수, 클래스), 문서 문자열, 반환 유형 또는 시그니처 등의 추가 정보가 포함될 수 있다. 이를 통해 사용자는 단순히 이름만 보는 것이 아니라, 함수의 매개변수 목록을 실시간으로 확인하거나 관련 문서를 툴팁으로 볼 수 있어 코딩 효율성과 정확성을 크게 높일 수 있다. 이 과정은 구문 분석과 정적 분석 기술에 기반한다.
언어 서버 프로토콜을 통해 코드 완성 기능이 표준화됨으로써, 파이썬이나 자바스크립트와 같은 특정 프로그래밍 언어를 위한 도구 개발자는 단 한 번의 서버 구현으로, 비주얼 스튜디오 코드, 빔, 이클립스 등 다양한 편집기에서 동일한 고품질의 완성 경험을 제공할 수 있게 되었다. 이는 기존에 각 편집기별로 플러그인을 따로 개발해야 했던 번거로움과 불일치 문제를 해결한다.
3.3. 진단 정보
3.3. 진단 정보
언어 서버 프로토콜의 진단 정보 기능은 코드 편집 과정에서 발생하는 오류, 경고, 정보 메시지 등을 실시간으로 개발자에게 제공하는 역할을 한다. 이는 린팅이나 정적 분석 도구의 결과를 통합 개발 환경에 통합하는 방식과 유사하지만, 언어 서버가 중앙에서 이 정보를 생성하고 관리한다는 점에서 차이가 있다. 서버는 소스 코드를 분석하여 문법 오류, 타입 불일치, 잠재적 버그, 스타일 위반 등 다양한 문제를 감지하고, 이를 진단 메시지로 변환하여 클라이언트에 전송한다.
클라이언트는 수신한 진단 정보를 코드 편집기의 사용자 인터페이스에 시각적으로 표시한다. 일반적으로 오류는 빨간색 물결선, 경고는 노란색 물결선, 정보는 파란색 물결선으로 코드 라인 아래에 표시되며, 사용자가 해당 표시에 마우스를 올리거나 클릭하면 상세한 메시지를 확인할 수 있다. 또한 문제 패널에 모든 진단 목록을 집계하여 보여주기도 한다. 이 과정은 문서가 변경될 때마다 비동기적으로 발생하므로, 개발자는 코드를 입력하는 즉시 피드백을 받을 수 있다.
진단 정보의 핵심은 정밀한 위치 정보를 포함한다는 점이다. 각 메시지는 문제가 발생한 파일 내의 정확한 범위(시작 줄, 시작 문자, 끝 줄, 끝 문자)와 심각도(오류, 경고, 정보, 힌트), 소스 코드, 관련 도움말 링크 등을 담고 있다. 이를 통해 클라이언트는 에디터에서 정확한 위치에 강조 표시를 할 수 있으며, 사용자는 문제의 정확한 컨텍스트를 이해하고 빠르게 수정할 수 있다. 이 기능은 디버깅 효율을 높이고 코드 품질을 유지하는 데 기여한다.
3.4. 정의로 이동
3.4. 정의로 이동
정의로 이동은 언어 서버 프로토콜이 제공하는 핵심 기능 중 하나로, 개발자가 소스 코드 편집기나 통합 개발 환경 내에서 특정 심볼(예: 함수명, 변수명, 클래스명)을 선택했을 때, 그 심볼의 선언 또는 정의가 위치한 소스 코드의 정확한 위치로 즉시 이동할 수 있게 해주는 기능이다. 이 기능은 코드베이스를 탐색하거나 외부 라이브러리의 구현을 이해할 때 필수적이다.
기능이 동작하려면 클라이언트(편집기)가 textDocument/definition 요청을 서버(언어 서버)로 보낸다. 이 요청에는 현재 문서의 URI와 커서 위치 정보가 포함된다. 서버는 이 정보를 분석해 해당 심볼의 정의 위치를 찾아, 하나의 위치(URI와 행/열 정보) 또는 여러 위치(오버로딩된 함수의 경우)를 응답으로 반환한다. 클라이언트는 이 응답을 받아 사용자를 해당 위치로 안내한다.
이 기능은 단순히 현재 파일 내부의 정의뿐만 아니라, 프로젝트 내 다른 파일이나 심지어 외부 의존성으로 설치된 패키지의 소스 코드로의 이동도 지원한다. 이를 통해 개발자는 디버깅이나 코드 리뷰 과정에서 맥락 전환 없이 효율적으로 코드를 탐색할 수 있으며, 생산성을 크게 향상시킨다.
3.6. 코드 액션
3.6. 코드 액션
코드 액션은 언어 서버 프로토콜의 핵심 기능 중 하나로, 소스 코드 내에서 특정 문제를 해결하거나 코드 품질을 개선하기 위한 수정 사항이나 리팩토링 작업을 제안하고 실행하는 기능이다. 이 기능은 주로 코드 편집기나 통합 개발 환경의 컨텍스트 메뉴나 전구 아이콘을 통해 사용자에게 제공된다. 예를 들어, 사용하지 않는 변수를 제거하거나, 특정 함수를 추출하거나, 오류를 수정할 수 있는 빠른 해결책을 제시하는 것이 코드 액션의 역할이다.
코드 액션은 일반적으로 진단 정보와 밀접하게 연동되어 작동한다. 언어 서버가 코드를 분석하여 발견한 경고나 오류 같은 진단 정보가 있을 때, 해당 문제를 해결할 수 있는 하나 이상의 코드 액션을 클라이언트에게 제안할 수 있다. 또한 특정 코드 범위나 위치에서 사용 가능한 일반적인 리팩토링 옵션을 제공하는 데에도 사용된다. 이를 통해 개발자는 반복적이고 수동적인 코드 수정 작업을 줄이고, 보다 효율적으로 코드 품질을 유지할 수 있다.
이 기능의 작동 방식은 클라이언트의 요청에 기반한다. 클라이언트는 특정 문서와 위치, 또는 진단 정보의 컨텍스트를 서버에 전송하여 사용 가능한 코드 액션 목록을 요청한다. 언어 서버는 이에 응답하여 제목, 종류, 그리고 실제 편집 내용을 포함한 액션 목록을 반환한다. 사용자가 특정 액션을 선택하면, 클라이언트는 서버에 해당 액션의 실행을 요청하고, 서버가 반환한 텍스트 편집 명령을 문서에 적용하여 코드를 변경한다.
코드 액션은 개발자의 생산성을 크게 향상시키는 강력한 도구로, 린팅 도구의 결과를 즉시 수정하거나, 리팩토링을 쉽게 적용할 수 있게 한다. 이는 마이크로소프트가 제안한 언어 서버 프로토콜의 주요 목적인, 다양한 코드 편집기와 통합 개발 환경에서 일관된 고급 언어 지원을 제공하는 데 기여하는 중요한 요소이다.
4. 구현 및 도구
4. 구현 및 도구
4.1. 프로토콜 사양
4.1. 프로토콜 사양
언어 서버 프로토콜의 사양은 공개된 표준 문서로 관리된다. 이 사양은 프로토콜의 핵심인 JSON-RPC 메시지 형식, 클라이언트와 서버 간의 초기화 및 종료 절차, 그리고 지원 가능한 모든 기능에 대한 요청과 응답의 구조를 상세히 정의한다. 사양 문서는 마이크로소프트가 주도하는 공개 저장소에서 유지되며, 버전별로 명확한 변경 이력을 제공한다.
사양은 크게 세 부분으로 구성된다. 첫째는 프로토콜의 기본 계층으로, JSON-RPC 2.0을 기반으로 한 헤더와 메시지 교환 규칙을 다룬다. 둘째는 언어 서버와 클라이언트의 수명 주기를 관리하는 일반 프로토콜 부분이다. 마지막으로 가장 방대한 부분은 텍스트 문서 동기화, 코드 완성, 정의로 이동, 진단 정보 제공 등 구체적인 언어 기능을 구현하기 위한 메서드와 알림, 데이터 타입을 정의한다.
이 공개 사양 덕분에 다양한 언어 커뮤니티와 벤더는 표준을 준수하는 자체 언어 서버를 구현할 수 있다. 또한 통합 개발 환경이나 소스 코드 편집기 제작사는 이 사양에 맞춰 클라이언트 측 기능을 통합하기만 하면, 사양을 준수하는 모든 언어 서버의 기능을 일관된 방식으로 사용자에게 제공할 수 있다. 이는 프로그래밍 언어 도구 생태계의 상호운용성을 크게 높이는 기반이 된다.
4.2. 서버 구현 예시
4.2. 서버 구현 예시
언어 서버 프로토콜의 사양이 공개된 이후, 다양한 프로그래밍 언어와 도구 생태계에서 이를 준수하는 서버 구현체들이 등장했다. 이는 프로토콜의 주요 목적인 편집기와 언어 도구의 분리를 실현하는 데 기여했다. 대표적인 예로 마이크로소프트가 주도적으로 개발한 TypeScript 언어 서버가 있으며, 이는 비주얼 스튜디오 코드에 기본적으로 통합되어 강력한 코드 완성 및 탐색 기능을 제공한다.
또한 파이썬용으로는 Microsoft Python Language Server와 Jedi 기반의 서버, 자바용 Eclipse JDT Language Server 등이 널리 사용된다. 커뮤니티 주도로 개발된 Rust의 rust-analyzer나 Go 언어의 gopls와 같은 서버들도 활발히 개발되고 있으며, 해당 언어의 생태계에서 사실상의 표준 도구로 자리 잡았다.
이러한 서버 구현체들은 대부분 오픈 소스로 개발되어 있으며, JSON-RPC를 통해 통신한다. 서버는 특정 프로그래밍 언어의 구문 분석, 정적 분석, 코드 모델링 등의 복잡한 작업을 처리하고, 그 결과를 프로토콜에 정의된 메시지 형식으로 클라이언트에 전송한다. 하나의 서버가 구현되면, 이를 지원하는 모든 통합 개발 환경이나 소스 코드 편집기에서 동일한 수준의 언어 지원을 받을 수 있다는 점이 가장 큰 장점이다.
서버 구현의 난이도는 언어의 복잡성에 따라 다르지만, 프로토콜 사양이 상세히 정의되어 있어 비교적 표준화된 방식으로 개발을 진행할 수 있다. 많은 구현체들이 Node.js나 .NET 같은 플랫폼 위에서 동작하며, 프로토콜의 핵심 기능인 텍스트 동기화, 진단 정보 제공, 정의로 이동 등을 충실히 구현하는 것을 목표로 한다.
4.3. 클라이언트 통합
4.3. 클라이언트 통합
클라이언트 통합은 통합 개발 환경이나 소스 코드 편집기가 언어 서버 프로토콜을 준수하는 언어 서버와 연결되어 작동하도록 만드는 과정을 말한다. 클라이언트 측에서는 프로토콜에서 정의한 JSON-RPC 메시지를 주고받기 위한 통신 모듈을 구현하고, 서버로부터 받은 정보를 편집기 사용자 인터페이스에 표시하는 역할을 담당한다. 이를 통해 코드 완성 목록이나 진단 정보 같은 기능이 편집기 화면에 통합되어 나타난다.
주요 통합 개발 환경과 편집기들은 대부분 언어 서버 프로토콜 클라이언트 지원을 내장하고 있거나, 확장 프로그램을 통해 이 기능을 추가할 수 있다. 예를 들어, 비주얼 스튜디오 코드는 자체적으로 강력한 LSP 클라이언트를 탑재하고 있으며, 이클립스, 인텔리J IDEA, 서브라임 텍스트, 네오빔, 이맥스 등도 공식 또는 커뮤니티 확장을 통해 통합을 지원한다. 클라이언트 통합의 핵심은 특정 프로그래밍 언어에 종속되지 않고, 프로토콜만 준수하면 어떤 언어 서버와도 연결할 수 있다는 점이다.
클라이언트를 구현하거나 설정할 때는 일반적으로 몇 가지 구성이 필요하다. 먼저 사용할 언어 서버의 실행 파일 경로나 시작 명령을 지정해야 한다. 또한, 서버가 어떤 파일 확장자나 문서 유형을 처리할지, 작업 영역의 루트 디렉토리는 어디인지 등의 정보를 전달한다. 일부 클라이언트는 서버의 초기화 옵션을 세밀하게 조정할 수 있는 기능도 제공한다.
이러한 통합 구조 덕분에 언어 개발자는 여러 편집기를 위한 별도의 플러그인을 개발할 필요 없이, 하나의 언어 서버만 구현하면 광범위한 도구 생태계에서 자신의 언어를 지원할 수 있게 된다. 반대로 편집기 개발자 역시 새로운 언어를 지원하기 위해 복잡한 파서나 언어 분석 엔진을 직접 구축하지 않고, 해당 언어의 표준 서버를 연결하기만 하면 된다. 이는 개발 도구 생태계의 효율성과 상호 운용성을 크게 높이는 데 기여한다.
5. 장점과 한계
5. 장점과 한계
언어 서버 프로토콜의 가장 큰 장점은 클라이언트-서버 모델을 채택하여 언어 서버와 클라이언트를 분리했다는 점이다. 이로 인해 언어별로 고유한 기능을 구현하는 서버를 하나만 개발하면, 통합 개발 환경이나 소스 코드 편집기 등 다양한 클라이언트에서 동일한 서버를 재사용할 수 있다. 이는 개발 생산성을 크게 향상시킨다. 언어 도구 개발자는 모든 편집기를 위한 별도의 플러그인을 만들 필요 없이 하나의 서버에만 집중할 수 있으며, 편집기 개발자는 풍부한 언어 지원 기능을 별도 구현 없이 통합할 수 있다. 결과적으로 Visual Studio Code, Vim, Emacs, Sublime Text 등 서로 다른 편집기에서도 동일한 수준의 코드 완성, 정의로 이동, 오류 진단 등의 기능을 일관되게 제공받을 수 있게 된다.
그러나 이 프로토콜에도 몇 가지 한계는 존재한다. 첫째, 통신이 JSON-RPC와 JSON을 기반으로 하기 때문에, 매우 큰 문서나 복잡한 요청을 처리할 때 성능 오버헤드가 발생할 수 있다. 특히 실시간으로 텍스트 동기화를 하며 많은 분석을 요구하는 경우 지연이 느껴질 수 있다. 둘째, 프로토콜이 표준화되어 있지만, 모든 언어 서버가 모든 기능을 완벽하게 구현하거나 동일한 방식으로 동작한다는 보장은 없다. 따라서 클라이언트 측에서는 서버의 구현 수준에 따른 차이를 처리해야 할 수도 있다. 마지막으로, 이 프로토콜은 주로 텍스트 기반의 프로그래밍 언어 지원에 최적화되어 있어, 그래픽 사용자 인터페이스 빌더나 시각적 모델링 도구와 같은 비텍스트적 요소가 강한 개발 도구와의 통합에는 한계가 있을 수 있다.
